#include "md5.h"
#include <openssl/evp.h>
#include <vector>

std::string MD5Calculator::calculate(const std::string &filepath)
{
    std::ifstream file(filepath, std::ios::binary);
    if (!file)
        throw std::runtime_error("Cannot open file: " + filepath);

    EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
    const EVP_MD *md = EVP_md5();
    unsigned char md_value[EVP_MAX_MD_SIZE];
    unsigned int md_len;

    if (mdctx == nullptr)
        throw std::runtime_error("MD5 context creation failed");
    if (EVP_DigestInit_ex(mdctx, md, nullptr) != 1)
    {
        EVP_MD_CTX_free(mdctx);
        throw std::runtime_error("MD5 initialization failed");
    }

    char buffer[1024 * 16];
    while (file.read(buffer, sizeof(buffer)) || file.gcount())
    {
        if (EVP_DigestUpdate(mdctx, buffer, file.gcount()) != 1)
        {
            EVP_MD_CTX_free(mdctx);
            throw std::runtime_error("MD5 update failed");
        }
    }

    if (EVP_DigestFinal_ex(mdctx, md_value, &md_len) != 1)
    {
        EVP_MD_CTX_free(mdctx);
        throw std::runtime_error("MD5 finalization failed");
    }
    EVP_MD_CTX_free(mdctx);

    std::stringstream ss;
    ss << std::hex << std::setfill('0');
    for (unsigned int i = 0; i < md_len; i++)
    {
        ss << std::setw(2) << static_cast<int>(md_value[i]);
    }

    return ss.str();
}

bool MD5Calculator::verify(const std::string &filepath, const std::string &expected_md5)
{
    return calculate(filepath) == expected_md5;
}